/* Copyright 2017, 2018, 2019, 2020 Gabriel Czernikier
 *
 * This file is part of Côtehaus.
 * Côtehaus is free software: you can redistribute it and/or modify
 * it under the terms of one of the following:
 * 1. The same dual license as that of Côtehaus itself, of which individual licenses are
 * the MIT License and the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 2. The MIT License.
 * 3. The GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * Côtehaus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * MIT License and the GNU General Public License for more details.
 * You should have received a copy of the MIT License and the GNU General Public License
 * along with Côtehaus.  If not, see <https://opensource.org/licenses/MIT> and <http://www.gnu.org/licenses/>.
 */

LIST_HEAD(top_code_head);  // struct top_code_st
static int top_code_list_size;
struct context_st *init_context();
int import_package_compar(struct ident_st *import, struct pkg_sch_ent_st *pkg_sch_ent);

struct ident_st *add_ident(struct ident_st *what, struct ident_st *where) {
	list_add_tail(&what->ident_list, &where->ident_list);
}
struct ident_st *init_ident(lgwseq_t ident) {
	struct ident_st *i=(struct ident_st*)malloc(sizeof(struct ident_st));
	INIT_LIST_HEAD(&i->ident_list);
	i->ident=ident;
	return i;
}
struct top_code_st *init_top_code() {
	struct top_code_st *tc = (struct top_code_st*)malloc(sizeof(struct top_code_st));
	tc->package=NULL;
	INIT_LIST_HEAD(&tc->imports_head);
	tc->context=init_context();

	LIST_HEAD_INIT(&tc->top_code_list);
	list_add(&tc->top_code_list, &top_code_head);
	top_code_list_size++;
	return tc;
}

void set_package(struct ident_st *package, struct top_code_st *top_code) {
	top_code->package=package;
	pkg_sch_ent_list_size++;
}
void add_import(struct ident_st *import, enum import_kind_en kind, struct top_code_st *top_code) {
	struct import_st *im= (struct import_st*)malloc(sizeof(struct import_st));
	im->kind=kind;
	im->import=*import;
	list_add_tail(&im->imports_list, &top_code->imports_head);

}
struct context_st *get_context(struct top_code_st *top_code) {
	return top_code->context;
}
struct context_st *open_context(struct context_st *current_context) {
	struct context_st *c = init_context();
	list_add_tail(&c->subcontext_list,&current_context->subcontext_head);
	c->enclosing=current_context;
	return c;
}
struct context_st *close_context(struct context_st *subcontext) {
	return subcontext->enclosing;
}
struct context_st *init_context() {
	struct context_st *c=(struct context_st*)malloc(sizeof(struct context_st));
	INIT_LIST_HEAD(&c->referentials_head);
	INIT_LIST_HEAD(&c->subcontext_head);
	INIT_LIST_HEAD(&c->subcontext_list);
	c->enclosing=NULL;
	return c;
}
void add_referential(lgwseq_t name, struct context_st *where) {
	struct refeterial_st *r=(struct referential_st*) malloc(sizeof(struct referential_st));
	r->name=name;
	INIT_LIST_HEAD(&r->referential_list);
	list_add_tail(&r->referential_list, &where->referentials_head);
	return r;
}

void resolve_imports() {
	struct top_code_st *what;
	list_for_each_entry(what, struct top_code_st, top_code_list, &top_code_head) {
		struct import_st *i;
		list_for_each_entry(i, struct import_st, imports_list, &what->imports_head) {
			// any case:
			// kind= type, member, type_glob, member_glob
			// import_length == package_length+type_path_length+member_length, import_length>=1
			//  package_length>=0, type_path_length>=0, member_length<=1

			// if kind==type or kind==member_glob => type_path_length>=1 and member_length==0
			if(i->kind==reftyp_import || i->kind==member_glob_import) {
				pkg_sch_ent=search_1(PACKAGE_SEARCH, NULL, i->import, ident_length(i->import)-n);  // n= 1 .. ident_length(i->import)-1
				rtyp_sch_ent=search_1(REFTYPE_SEARCH, package, ident_at(i->import,a+1),1);  // a=n .. ident_length(i->import)-1
			}
			// if kind==member => type_path_length>=1 and member_length==1
			// if kind==type_glob =>type_path_length==0, member_length==0
			if(i->kind==reftyp_glob_import)
				pkg_sch_ent=bsearch(i->import, pkg_sch_ent_ary,pkg_sch_ent_list_size,sizeof(struct pkg_sch_ent_st),(int (*)(const void *, const void *))import_package_compar);
		}
	}
}

struct pkg_sch_ent_st {
	struct ident_st package;
	struct context_st *refs_in_package;
	/* the next class as per order if &this<equiv_class_vector.
	 * the same class as per order if equiv_class_vector<&this
	 */
	struct pkg_sch_ent_st *equiv_class_vector;
}

void setup_package_search_entry_ary() {

	if(top_code_list_size==0)
		return;
	pkg_sch_ent_ary = (struct pkg_sch_ent_st*)malloc(top_code_list_size*sizeof(struct pkg_sch_ent_st));
	struct top_code_st *from;
	unsigned int i=0;
	list_for_each_entry(from, struct top_code_st, top_code_list, top_code_head) {
		(pkg_sch_ent_ary +i++)->package =from->package; 
		(pkg_sch_ent_ary +i++)->refs_in_package =from->context; 
	}
	qsort(pkg_sch_ent_ary, top_code_list_size, sizeof(struct pkg_sch_ent_st), (int (*)(const void *, const void *))pgk_sch_ent_st_compar);
	unsigned int i=1;
	unsigned int representative_i = 0;
	while(i<top_code_list_size) {
		cmp=pkg_sch_ent_st_compar(&pkg_sch_ent_ary[representative_i], &pkg_sch_ent_ary[i]);
		if(cmp!=0){
			pkg_sch_ent_ary[representative_i].equiv_class_vector = &pkg_sch_ent_ary[i];
			representative_i = i;
		}
		else
			pkg_sch_ent_ary[i].equiv_class_vector = &pkg_sch_ent_ary[representative_i];
		i++;
	}
	pkg_sch_ent_ary[representative_i].equiv_class_vector = NULL;
}

struct ident_mark {
	struct list_head marks_head;
	struct ident_st *mark;
};

// referential or qualifier search entry
struct maybequalif_schent {
	union {
		struct ident_mark justqualif;
		lgwseq_t maybequalif;
	} schent; // the search entry
	/* false if a referential, true if a qualifier as a slice of a package
	 * for example, if "a.b" is a package, and "c" is a referential therein ("a.b.c"),
	 * so "a" and "b" are package slices. Thus:
	 * schent.justqualif=="a" && just_qualif==true
	 * schent.justqualif=="b" && just_qualif==true
	 * schent.maybequalif=="c" && just_qualif==false
	 */
	bool just_qualif; 
	struct context_st *subcontext;
	struct maybequalif_schent *eqcls_vec;
	struct maybequalif_schent *qualifiee_base;
	size_t qualifiee_nelems;
};
LIST_HEAD(maybequalif_head);

#define ARRAY_APPEND(ARRAY,ARYLEN,DATA,INDEX) while((INDEX)>=(ARYLEN)) \
	(ARYLEN) = (ARYLEN)==0?512:(ARYLEN)*2; \
	if(((ARRAY) = realloc((ARRAY), (ARYLEN)*sizeof((DATA))))==NULL) { \
		fprintf(stderr, "%s: %s.", __func__, strerror(ENOMEM)); \
		exit(EXIT_FAILURE); \
	} \
	(ARRAY)[INDEX] = (DATA); \
	(INDEX) ++;

struct maybequalif_schent *mayqual_ary;
size_t ary_len;
size_t at_end_idx;

lgwseq_t schent(struct maybequalif_schent *mayqual) {
	return mayqual->just_qualif?mayqual->schent.ident_mark->mark.ident:mayqual->schent.justqualif;
}

int maybequalif_schent_compar(const struct maybequalif_schent *a, const struct maybequalif_schent *b) {
	return lgwseq_t_compar(&schent(a),&schent(b));
}

/* Set up a data structure of anithing that may be a qualifier, regardless if indeed it is.
 * X may be a qualifier if any of the following expressions denotes a referential:
 * X.Y.Z, Y.X.Z, even Y.Z.X, otherwise noting about X doesn't match to be a qualifier
 * unless there's anithing for X to qualify as in Y.Z.X.A, but the view of X in isolation cannot
 * tell that, so X may be taken as a qualifier (in a might sense) even when it isn't.
 * This is the logic (reasoning) behind this function to work.
 */
void setup_maybequalif_search__ary() {
	mayqual_ary = (struct maybequalif_schent*) malloc(sizeof(struct maybequalif_schent));
	struct maybequalif_schent *course=mayqual_ary;
	ary_len = 1,
	at_end_idx = 1;
	course->qualifiee_nelems=0;
	course->qualifiee_base =at_end_idx;
	struct top_code_st *top_code;
	struct maybequalif_schent entry;
	struct referential_st *referential;
	struct context_st *subcontext;
	// take the start mark of the packages, and the top level (default package) referentials
	list_foreach_entry(top_code,struct top_ code_st,top_code_list,top_code_head) {
		if(top_code->package!=NULL) {
			struct ident_mark justqualif;
			justqualif.mark=top_code->package;
			justqualif.marks_head=top_code->package->ident_list;
			entry.schent.justqualif=justqualif;
			entry.just_qualif = true;
			entry.subcontext = top_code->context;
			course->qualifiee_nelems++;
			ARRAY_APPEND(mayqual_ary, ary_len, entry, at_end_idx);
		}
		else {
			do {
				list_peek_next_entry(referential, struct referential_st, referential_list, &top_code->subcontext->referentials_head);
				if(referential==NULL)
					break;
				list_peek_next_entry(subcontext, struct context_st, subcontext_list, &top_code->subcontext->subcontext_head);
				entry.schent.maybequalif=referential;
				entry.just_qualif=false;
				entry.subcontext=subcontext;
				course->qualifiee_nelems++;

				ARRAY_APPEND(mayqual_ary, ary_len, entry, at_end_idx);

			} while(1);
		}
	}  // list_foreach_entry(top_code,...)
	if(course->qualifiee_nelems!=0)
		expand_qualifiees(course, &at_end_idx, &ary_len);
}

void expand_qualifiees(struct maybequalif_schent *bounds, size_t *at_end_idx, size_t *ary_len) {
	struct maybequalif_schent *eqcls_other = bounds->qualifiee_base,
	*eqcls_repre;

	qsort(bounds->qualifiee_base, bounds->qualifiee_nelems,sizeof(bounds), (int(*)(const void*,const void*)maybequalif_schent_compar/*TODO*/));
	while(bounds->qualifiee_base+bounds->qualifiee_nelems!=eqcls_other) {
		eqcls_repre=eqcls_other;
		eqcls_repre->qualifiee_base=at_end_idx;

		while(bounds->qualifiee_base+bounds->qualifiee_nelems!=eqcls_other&&!maybequalif_schent_compar(eqcls_repre, eqcls_other)) {
			bare_expand(eqcls_other,eqcls_repre,&at_end_idx,&ary_len);
			if(eqcls_other==eqcls_repre)
				eqcls_repre->eqcls_vec=NULL;
			else
				eqcls_other->eqcls_vec=eqcls_repre;
			eqcls_other++;
		}
		if(bounds->qualifiee_base+bounds->qualifiee_nelems==eqcls_other)
			eqcls_repre->eqcls_vec=NULL;
		else
			eqcls_repre->eqcls_vec=eqcls_other;

	}

	for(struct maybequalif_schent *course=bounds->qualifiee_base; course!=NULL&&course<bounds->qualifiee_base+bounds->qualifiee_nelems; course=course->eqcls_vec)
		if(course->nelems!=0)
			expand_qualifiees(course,at_end_idx,ary_len);
}

/* Return the number of expanded qualifiees
 */
int bare_expand(struct maybequalif_schent *what) {
	struct maybequalif_schent spawn;
	if(what->just_qualif && (struct ident_st *qualif=list_peek_next_entry(what->schent.justqualif.mark,struct ident_st,ident_list,&what->schent.justqualif.marks_head))!=NULL) {
		// spawn a bare qualifier (just a qualifier)
		spawn=*what;
		spawn.qualifiee_nelems=0;
		spawn.schent.justqualif.mark=qualif;  // mark one ident slice ahead. say mark was "a" as now is "b" in "a"->"b" ("a" and "b" are ident slices)
		ARRAY_APPEND(mayqual_ary, ary_len, spawn, at_end_idx);
		return 1;
	}
	else {
		int nelems = 0;
		struct referential_st *referential=list_peek_entry(struct referential_st, referential_list, &what->context->referentials_head);
		struct context_st *context=list_peek_entry(struct context_st, subcontext_list, &what->context->subcontext_head);
		while(referential!=NULL) {
			// spawn a context refetential
			spawn.schent.maybequalif=referential;
			spawn.just_qualif=false;
			spawn.subcontext=subcontext;
			nelems++;

			ARRAY_APPEND(mayqual_ary, ary_len, spawn, at_end_idx);

			referential=list_peek_next_entry(referential, struct referential_st, referential_list, &what->context->referentials_head);
			subcontext=list_peek_next_entry(subcontext, struct context_st, subcontext_list, &what->context->subcontext_head);
		}
		return nelems;
	}
}

struct pkg_sch_ent_st search_package(struct ident_st pkg) {

}

int pkg_sch_ent_st_compar(struct pkg_sch_ent_st *a, struct pkg_sch_ent_st *b) {
	return ident_st_compar(&a->package, &b->package);
}


int import_package_compar(struct ident_st *import, struct pkg_sch_ent_st *pkg_sch_ent) {
	return ident_st_compar(import,&pkg_sch_ent->package);
}


int ident_st_compar(struct ident_st *a, struct ident_st *b) {
	int cmp=lgwseq_t_compar(&a->ident,&b->ident);
	if(cmp)
		return cmp;
	struct ident_st *a_course=a, 
			*b_course=b;
	do {
		a_course=list_peek_next_entry(a_course, struct ident_st, ident_list, &a.ident_list);
		b_course=list_peek_next_entry(b_course, struct ident_st, ident_list, &b.ident_list);
		if(!a_course && !b_course)
			return 0;
		if(!a_course)
			return -1;
		if(!b_course)
			return 1;
		cmp=lgwseq_t_compar(&a_course.ident, &b_course.ident);
	}
	while(!cmp);
	return cmp;
}
